﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using MC2Development.DevWebServer.Helpers;
using MC2Development.DevWebServer.Interfaces;

namespace MC2Development.DevWebServer
{
    /// <summary>
    /// The default request processor
    /// </summary>
    internal class StandardRequestProcessor : IRequestProcessor
    {
        /// <summary>
        /// Processes an incoming request
        /// </summary>
        /// <param name="context">The Http context associated with the incoming request</param>
        /// <param name="serverParameters">Server initialisation parameters</param>
        void IRequestProcessor.ProcessRequest(HttpListenerContext context, ServerParameters serverParameters)
        {
            using (new Tracer())
            {
                HttpListenerResponse response = context.Response;
                HttpListenerRequest request = context.Request;

                // Authenticate
                if (Authenticate(serverParameters.Authentication, request))
                {
                    // Delay
                    Delay(serverParameters.Delay);
                    
                    // Validate request body
                    if (serverParameters.ValidateRequestBodyExists && !request.HasEntityBody)
                    {
                        Tracer.WriteTraceEntry("Invalid body content. Returning a 400 (Bad Request) status");
                        response.StatusCode = (int)HttpStatusCode.BadRequest;
                    }
                    else
                    {
                        // Main response processing
                        Tracer.WriteTraceEntry("Processing request for a resource...");

                        // Get the name of the resource requested
                        String strResponseFileName = request.Url.PathAndQuery.Remove(0, 1);
                        Tracer.WriteTraceEntry("Resource requested: {0}", strResponseFileName);

                        string strResourcePath = Path.Combine(serverParameters.RequestHandler.ContentPath, strResponseFileName);

                        if (File.Exists(strResourcePath))
                        {
                            // Headers - write these before the body content!
                            if (!string.IsNullOrEmpty(serverParameters.ContentType))
                            {
                                response.ContentType = serverParameters.ContentType;
                            }
                            response.StatusCode = (int)HttpStatusCode.OK;

                            // Body
                            Tracer.WriteTraceEntry("Retrieving resource from file system at {0}", strResourcePath);
                            using (FileStream fs = new FileStream(strResourcePath, FileMode.Open, FileAccess.Read))
                            {
                                fs.WriteTo(response.OutputStream);
                            }
                        }
                        else
                        {
                            Tracer.WriteTraceEntry("File system resource does not exist: {0}", strResourcePath);
                            response.StatusCode = (int)HttpStatusCode.NotFound;
                        }
                    }
                }
                else
                {
                    response.StatusCode = (int)HttpStatusCode.Forbidden;
                }

                response.OutputStream.Close();
            }
        }

        /// <summary>
        /// Simple request handler
        /// </summary>
        /// <param name="context">The Http context associated with the incoming request</param>
        internal static void BasicRequestHandler(HttpListenerContext context)
        {
            context.Response.StatusCode = (int)HttpStatusCode.OK;
            context.Response.OutputStream.Close();
        }

        /// <summary>
        /// Authenticates the request
        /// </summary>
        /// <param name="authenticationParameters">Authentication parameters</param>
        /// <param name="request">Http request</param>
        /// <returns>Boolean flag to indicate authentication success</returns>
        private static bool Authenticate(Authentication authenticationParameters, HttpListenerRequest request)
        {
            bool blnAuthenticated = true;

            if (authenticationParameters != null)
            {
                // Get authenticator
                IAuthenticator authenticator = (authenticationParameters.CustomAuthenticator ?? new Authenticator()); 

                switch (authenticationParameters.AuthenticationType)
                {
                    case AuthenticationType.None:
                        blnAuthenticated = true;
                        break;
                    
                    case AuthenticationType.Basic:
                        Tracer.WriteTraceEntry("Retrieving basic authentication details");
                        Credentials suppliedCredentials = GetBasicAuthenticationCredentials(request);

                        Tracer.WriteTraceEntry("Authenticating request");
                        blnAuthenticated = authenticator.AuthenticateUser(suppliedCredentials, authenticationParameters.AuthenticationCredentials);

                        Tracer.WriteTraceEntry("The request {0} authenticated", (blnAuthenticated ? "was" : "was not"));
                        break;

                    default:
                        blnAuthenticated = true;
                        break;
                }
            }

            return blnAuthenticated;
        }

        /// <summary>
        /// Retrieves the basic authentication credentials supplied in the Http Authorization header
        /// </summary>
        /// <param name="request">Http request</param>
        /// <returns>A populated credentials instance</returns>
        private static Credentials GetBasicAuthenticationCredentials(HttpListenerRequest request)
        {
            String strAuthRaw = request.Headers["Authorization"];
            if (!String.IsNullOrEmpty(strAuthRaw))
            {
                String strAuth = Encoding.ASCII.GetString(System.Convert.FromBase64String(request.Headers["Authorization"].Remove(0, 6)));
                String[] arrSuppliedCredentials = strAuth.Split(':');

                return new Credentials()
                {
                    Username = arrSuppliedCredentials[0],
                    Password = arrSuppliedCredentials[1]
                };
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Deplay the request processing for the specified duration
        /// </summary>
        /// <param name="delayDuration">Delay duration in milliseconds</param>
        private static void Delay(int delayDuration)
        {
            if (delayDuration > 0)
            {
                Tracer.WriteTraceEntry("Delaying processing for {0}ms...", delayDuration.ToString());
                Thread.Sleep(delayDuration);
            }
        }

    }

}
